<?php

namespace fakis\core\base;

use Yii;
use yii\base\ErrorException;
use yii\base\Exception;
use yii\base\Model;
use yii\base\UserException;
use yii\data\Pagination;
use yii\web\HttpException;
use yii\web\UnprocessableEntityHttpException;

/**
 * Class Customizer
 *
 * @author Fakis <fakis738@qq.com>
 */
class Customizer extends \yii\rest\Serializer
{
    public $codeEnvelope = 'code';
    public $statusEnvelope = 'status';
    public $messageEnvelope = 'msg';
    public $dataEnvelope = 'data';
    public $metaEnvelope = 'meta';
    public $traceEnvelope = 'trace';

    /**
     * @var string
     */
    public $collectionEnvelope = 'items';

    /**
     * 格式化
     * @param mixed $data
     * @param int $code
     * @return array
     */
    public function format($data, int $code = 0)
    {
        $result = [
            $this->codeEnvelope => 0,
            $this->statusEnvelope => 200,
            $this->messageEnvelope => '',
            $this->dataEnvelope => new \stdClass(),
        ];

        if ($data instanceof \Throwable) {
            $result = $this->serializeException($data);
        } elseif ($code > 0) {
            $exception = new HttpException($code, is_string($data) ? $data : null);
            $result = $this->serializeException($exception);
        } elseif ($data instanceof Model && $data->hasErrors()) {
            $messages = [];
            foreach ($data->getFirstErrors() as $name => $error) {
                $messages[] = ['field' => $name, 'message' => $error];
            }
            $message = $messages[0]['message'];

            $exception = new UnprocessableEntityHttpException($message, 10422);
            $result = $this->serializeException($exception, $messages);
        } else {
            $result[$this->dataEnvelope] = $this->serialize($data);
        }

        return $result;
    }

    /**
     * 序列化错误信息
     * @param \Throwable $exception
     * @return array
     */
    protected function serializeException($exception, $errorInfo = null)
    {
        if (!YII_DEBUG && !$exception instanceof UserException && !$exception instanceof HttpException) {
            $exception = new HttpException(500, Yii::t('yii', 'An internal server error occurred.'));
        }

        $name = ($exception instanceof Exception || $exception instanceof ErrorException) ? $exception->getName() : 'Error';
        $message = $exception->getMessage();
        $result = [
            $this->codeEnvelope => $exception->getCode() === 0 ? 1 : $exception->getCode(),
            $this->statusEnvelope => $exception instanceof HttpException ? $exception->statusCode : 400,
            $this->messageEnvelope => $message !== '' ? $message : $name,
            $this->dataEnvelope => $errorInfo ?? new \stdClass(),
        ];

        if (YII_DEBUG) {
            $result[$this->traceEnvelope]['name'] = $name;
            $result[$this->traceEnvelope]['target'] = get_class($exception);
            if (!$exception instanceof UserException) {
                $result[$this->traceEnvelope]['file'] = $exception->getFile();
                $result[$this->traceEnvelope]['line'] = $exception->getLine();
                $result[$this->traceEnvelope]['stack-trace'] = explode("\n", $exception->getTraceAsString());
                if ($exception instanceof \yii\db\Exception) {
                    $result[$this->traceEnvelope]['error-info'] = $exception->errorInfo;
                }
            }
            if (($prev = $exception->getPrevious()) !== null) {
                $result[$this->traceEnvelope]['previous'] = $this->serializeException($prev);
            }
        }

        return $result;
    }

    /**
     * Serializes a pagination into an array.
     * @param Pagination $pagination
     * @return array the array representation of the pagination
     * @see addPaginationHeaders()
     */
    protected function serializePagination($pagination)
    {
        return [
            $this->metaEnvelope => [
                'totalCount' => $pagination->totalCount,
                'pageCount' => $pagination->getPageCount(),
                'currentPage' => $pagination->getPage() + 1,
                'perPage' => $pagination->getPageSize(),
            ],
        ];
    }

    /**
     * Adds HTTP headers about the pagination to the response.
     * @param Pagination $pagination
     */
    protected function addPaginationHeaders($pagination)
    {
        $this->response->getHeaders()
            ->set($this->totalCountHeader, $pagination->totalCount)
            ->set($this->pageCountHeader, $pagination->getPageCount())
            ->set($this->currentPageHeader, $pagination->getPage() + 1)
            ->set($this->perPageHeader, $pagination->getPageSize());
    }

    /**
     * @return array
     */
    protected function getRequestedFields()
    {
        $request = $this->request;
        $fields = $request->post($this->fieldsParam, $request->get($this->fieldsParam));
        $expand = $request->post($this->expandParam, $request->get($this->expandParam));

        return [
            is_string($fields) ? preg_split('/\s*,\s*/', $fields, -1, PREG_SPLIT_NO_EMPTY) : [],
            is_string($expand) ? preg_split('/\s*,\s*/', $expand, -1, PREG_SPLIT_NO_EMPTY) : [],
        ];
    }
}